-- AutoFightM
-- created by panyl
-- 自动战斗

module("AutoFightM", package.seeall);

local AUTO_START = 1;
local AUTO_PAUSE = 2;
local AUTO_STOP  = 3;

local pauseTime  = 0;
local forbidNext = 0;

-- 每一步的执行时间
local moveTime = 0.3;

-- 一开始是停止状态的
local state = AUTO_STOP;

-- 当前层某一策略执行次数
local layerRuleTimes = {};
-- 当前迷宫某一策略执行次数
local dungeonRuleTimes = {};

-- 可以翻开的格子队列、活着的怪物（已翻开）、物品
local items = {};
local ableToOpen = {};
local aliveMonsters = {};
local supplyItems = {};
local featureItems = {};
local specialGrids = {};

-- 配置
local tacticsCfg = {};
-- 策略配置
local strategyCfg = {};
-- 模板配置
local templateCfg = {};
-- 策略子规则
local rules = {};

-- 当前使用模板，默认是1
local nowTemplate = 1;

-- 载入配置表
local _init = false;
local loadCsv = function()
    if _init then return end
    _init = true;

    -- 卷轴信息配置
    local tactics_CSV = get_auto_fight_CSV();
    for i = 1, #tactics_CSV do
        tacticsCfg[tactics_CSV[i].tactics_id] = tactics_CSV[i];
    end

    -- 自动战斗策略集
    local auto_fight_strategy_CSV = get_auto_fight_strategy_CSV();
    for i = 1, #auto_fight_strategy_CSV do
        strategyCfg[auto_fight_strategy_CSV[i].id] = auto_fight_strategy_CSV[i];
    end
    auto_fight_strategy_CSV = {};

    -- 自动战斗模板
    local auto_fight_template_CSV = get_auto_fight_template_CSV();
    for i = 1, #auto_fight_template_CSV do
        templateCfg[auto_fight_template_CSV[i].id] = auto_fight_template_CSV[i];
    end
    auto_fight_template_CSV = {};
end

-- 模块初始化操作
function init()
    if not _init then
        -- 策略子规则
        rules = LOAD_PATH2("game/logic/module/auto_fight");
        loadCsv();
    end

    -- 注册事件
    registerEvent();
end

-- 注册事件
function registerEvent()
    -- 先全部移除事件回调
    EventMgr.removeAll("AutoFightM");

    -- 进入下一层
    EventMgr.register("AutoFightM", event.START_NEXT_FLOOR, function()
        -- 清除执行记录
        layerRuleTimes = {};

        -- 先暂停一下
        pause();
    end);

    -- 关注开始新一层地牢事件
    EventMgr.register("AutoFightM", event.DUNGEON_AREADY, function()
        -- 清除已经收集的信息
        clearData();

        -- 清除执行记录
        layerRuleTimes = {};

        if TestFightM.isInTest() then
            -- 自动战斗测试
            setPauseTime(os.time() + 2);
            start();
        end

        local dungeonId = DungeonM.getDungeonId();
        local layer = DungeonM.currentLayer();
        if DungeonBossM.isBossLayer(dungeonId, layer) and haveBoss() then
            -- boss层需要先暂停
            pause();
        else
            -- 继续
            continue();
        end

        -- 如果是自动开启新的地图，开始自动战斗
        local autoStart = ME.user.dbase:queryTemp("auto_start", 0);
        if autoStart == 1 then
            -- 开启自动战斗
            start();
            ME.user.dbase:deleteTemp("auto_start");
        end
    end);

    -- 关注离开地牢事件
    EventMgr.register("AutoFightM", event.LEAVE_DUNGEON, function()
        -- 离开就停止
        stop();

        -- 清除执行记录
        dungeonRuleTimes = {};

        local autoPass = ME.user.dbase:queryTemp("auto_pass", 0);
        if type(autoPass) == "table" and autoPass.pass then
            local nextId = DungeonAreaM.getNextChildArea(autoPass.id);
            -- 如果通过所有子区域，则进行下下一区域
            DungeonServiceM.startEnterService(nextId);
            ME.user.dbase:deleteTemp("auto_pass");
            ME.user.dbase:setTemp("auto_start", 1);
        end
    end);

    -- 关注玩家死亡事件
    EventMgr.register("AutoFightM", event.HERO_DIE, function()
        -- 玩家死亡就暂停
        stop();
    end);

    -- 关注神龙许愿事件
    EventMgr.register("AutoFightM", event.WISH_TRIGGER, function(para)
        -- 神龙许愿
        if para.isOpenForm then
            stop();
        end
    end);

    -- BOSS出场光效播完之后
    EventMgr.register("AutoFightM", event.BOSS_SHOW, function(para)
        -- 尝试继续
        continue();
    end);

    -- 清怪之后
    EventMgr.register("AutoFightM", event.MONSTER_CLEAR, function(para)
        -- 一段时间之内不允许下楼
        forbidNext = os.time() + 5.4;
    end);

    -- 祭坛操作之后
    EventMgr.register("AutoFightM", event.DO_ALTAR_EVENT, function(para)
        -- 暂时不许动
        local count = para.count;

        pauseTime = os.time() + (count / 10);
    end);

    -- 注册通关迷宫的事件回调
    EventMgr.register("AutoFightM", event.DUNGEON_PASS, function(para)
        -- 如果是自动战斗状态下通关的，标记一下
        if state == AUTO_START then
            ME.user.dbase:setTemp("auto_pass", {pass = true, id = para.dungeonId});
        end
    end);
end

-- 是否有BOSS
function haveBoss()
    -- 获取所有格子
    local curDungeon = DungeonM.getCurrentDungeon();
    for _, grid in pairs(curDungeon) do
        -- boss
        if grid.type == GRID_TYPE_BOSS then
           return true;
        end
    end

    return false;
end

-- 获取当前自动战斗状态
function getState()
    return state;
end

-- 当前是否处于自动战斗
function inFighting()
    return state == AUTO_START;
end

-- 开/关
function switch()
    if state == AUTO_START then
        stop();
    else
        start();
    end
end

-- 开始
function start()
    if state == AUTO_START then
        return;
    end

    -- 先改变状态
    state = AUTO_START;

    executeTactics();

    EventMgr.fire(event.START_AUTO_FIGHT);
end

-- 停止
function stop()
    if state == AUTO_STOP then
        return;
    end

    -- 直接修改状态
    state = AUTO_STOP;

    EventMgr.fire(event.STOP_AUTO_FIGHT);
end

-- 暂停
function pause()
    -- 如果不是开始状态的，不能暂停
    if state ~= AUTO_START then
        return false;
    end

    -- 暂停状态
    state = AUTO_PAUSE;

    EventMgr.fire(event.STOP_AUTO_FIGHT);
end

-- 继续
function continue()
    if state ~= AUTO_PAUSE then
        return;
    end

    start();
end

-- 执行自动战斗策略
function executeTactics()
    -- 如果当前状态不是start
    if state ~= AUTO_START then
        return;
    end

    -- 如果不是在地牢中
    if DungeonM.getState() ~= DUNGEON_GAMING then
        return;
    end

    if LUA_COVERAGE  == 1 then
        moveTime = 1.5;
    end

    -- 确保连接上
    ScheduleM.createScheme("AutoFightM", daemon, moveTime, true)
end

-- 定时回调策略
function daemon()
    -- 一定要start状态才能走进来
    if state ~= AUTO_START then
        ScheduleM.deleteScheme("AutoFightM");
        return;
    end

    -- 不在游戏中
    if not ME.isInGame then
        stop();
        return;
    end

    -- 收集信息
    collectData();

    -- 执行操作
    move();
end

-- 获取forbidNext时间
function getForbidNext()
    return forbidNext;
end

-- 获取当前使用模板
function getTemplate()
    return nowTemplate;
end

-- 设置当前使用模板
function setTemplate(template)
    nowTemplate = template;
end

-- 获取配置表
function getTacticsCfg()
    return tacticsCfg;
end

-- 获取当前收集的信息
function getData()
    return {
        ["aliveMonsters"] = aliveMonsters,
        ["items"]         = items,
        ["ableToOpen"]    = ableToOpen,
        ["supplyItems"]   = supplyItems,
        ["featureItems"]  = featureItems,
        ["specialGrids"]  = specialGrids,
    };
end

-- 清除收集的信息
function clearData()
    items         = {};
    ableToOpen    = {};
    supplyItems   = {};
    featureItems  = {};
    aliveMonsters = {};
    specialGrids  = {};
end

-- 每一步执行前先统一收集信息
-- （放在子规则里会可能会处理很多次，
-- 直接在模块里统一收集一次即可）
function collectData()
    -- 可能可以拾取物品的格子类型
    local gridTypes = {
        GRID_TYPE_MONSTER,    -- 怪物
        GRID_TYPE_BOSS,       -- boss
        GRID_TYPE_ITEM,       -- 物品
        GRID_TYPE_BOX,        -- 宝箱
        GRID_TYPE_SLIME,      -- 冈布奥
        GRID_TYPE_APPRENTICE, -- 学徒
        GRID_TYPE_MAP,        -- 地图
        GRID_TYPE_BOSS_BOX,   -- boss宝箱
        GRID_TYPE_ACTIVITY_BOSS_BOX,  -- 活动宝箱
        GRID_TYPE_SUMMON,     -- 召唤兽
        GRID_TYPE_TRAP,       -- 陷阱
        GRID_TYPE_BEEHIVE,    -- 蜂巢
    };

    -- 重新收集一次数据
    items         = {};
    ableToOpen    = {};
    supplyItems   = {};
    featureItems  = {};
    aliveMonsters = {};
    specialGrids  = {};

    for pos = 1, GRID_SIZE do
        local grid = DungeonM.getGridByPos(pos);
        if DungeonM.canOpenGrid(pos) == GRID_OPEN_OK then
            -- 待翻开的格子
            table.insert(ableToOpen, pos);
        elseif grid:isOpened() and grid.state ~= GRID_STATE_DISABLE
            and grid.monster and not grid.monster:isDead() then
            -- 存活的怪物
            table.insert(aliveMonsters, pos);
        elseif table.indexOf(gridTypes, grid.type) ~= -1 and
            grid:isOpened() and grid.bonus and
            (not grid.monster or grid.monster:isDead()) then
            -- 物品
            if grid.bonus[2] == "max_hp" or grid.bonus[2] == "hp" or
                grid.bonus[2] == "max_hp" or grid.bonus[2] == "mp" then
                table.insert(supplyItems, pos);
                table.insert(items, pos);
            elseif not grid.display or DungeonServiceM.isMonsterClear() then
                table.insert(items, pos);
            end
        end

        -- 收集有autoFight模块的格子（需要已开启/未失效）
        local mod = DungeonM.getModule(grid.class);
        if grid:isOpened() and grid.state ~= GRID_STATE_DISABLE
            and mod and mod.autoFight then
            table.insert(specialGrids, pos);
        end

        -- TODO：自动拾取奖励，暂时只处理最后一层
        if DungeonM.isMaxLayer() then
            if grid.type == GRID_TYPE_FEATURE_ITEM
                and grid.state ~= GRID_STATE_DISABLE then
                -- 功能引导道具
                table.insert(featureItems, pos);
            end
        end
    end
end

-- 获取策略模板
function getModule(ruleName)
    local rule = rules[ruleName];
    if rule == "TBL" then
        rules[ruleName] = LOAD_RUN("game/logic/module/auto_fight/" .. ruleName);
        return rules[ruleName];
    end

    return rule;
end

-- 设置暂停时间
function setPauseTime(setTime)
    pauseTime = setTime;
end

-- 尝试操作
function move()
    -- 暂时不能操作
    if pauseTime and pauseTime > os.time() then
        return;
    end

    -- 抛事件
    EventMgr.fire(event.AUTO_FIGHT_MOVE, {});

    -- 当前模板策略集
    local strategy = templateCfg[nowTemplate]["rule"];

    -- 按策略集找到当前当前可以进行的操作
    for _, id in pairs(strategy) do
        local ruleName = strategyCfg[id]["rule"];
        local layerMaxTimes = strategyCfg[id]["layer_max_times"];
        local dungeonMaxTimes = strategyCfg[id]["dungeon_max_times"];
        local args = strategyCfg[id]["args"];
        local mod = getModule(ruleName);
        local layerTimes = tonumber(layerRuleTimes[ruleName]);
        local dungeonTimes = tonumber(dungeonRuleTimes[ruleName]);

        -- 执行操作
        if type(layerMaxTimes) == "number" then
            -- 有单层最大次数限制
            if layerTimes < layerMaxTimes then
                if mod and mod.canTrigger(args) then
                    -- 有单层限制的不论是否触发成功都要记录！！！
                    -- （因为有一些是每层初始概率奖励）
                    layerRuleTimes[ruleName] = layerTimes + 1;
                    if catch(function() return mod.trigger(args); end) then
                        cclog("auto fight：" .. ruleName);
                        -- 记录次数
                        local num = TestFightM.getSpecialLog(ruleName) or 0;
                        TestFightM.setSpecialLog(ruleName, num + 1);
                        return;
                    end
                end
            end
        elseif type(dungeonMaxTimes) == "number" then
            -- 有迷宫最大次数限制
            if dungeonTimes < dungeonMaxTimes then
                if mod and mod.canTrigger(args)
                    and catch(function() return mod.trigger(args); end) then
                    cclog("auto fight：" .. ruleName);
                    -- 记录下执行次数
                    dungeonRuleTimes[ruleName] = dungeonTimes + 1;
                    -- 记录次数
                    local num = TestFightM.getSpecialLog(ruleName) or 0;
                    TestFightM.setSpecialLog(ruleName, num + 1);
                    return;
                end
            end
        else
            -- 没限制的
            if mod and mod.canTrigger(args) and
                catch(function() return mod.trigger(args); end) then
                cclog("auto fight：" .. ruleName);
                -- 记录次数
                local num = TestFightM.getSpecialLog(ruleName) or 0;
                TestFightM.setSpecialLog(ruleName, num + 1);
                return;
            end
        end
    end

    -- TODO没有可以做的？
end

-- 判断能否自动战斗
function canAutoFight()
    return false;
end

-- 是否可以捡
function canPickup(grid)
    if (grid.type == GRID_TYPE_MONSTER or
        grid.type == GRID_TYPE_BOSS or
        grid.type == GRID_TYPE_ITEM or
        grid.type == GRID_TYPE_BOX or
        grid.type == GRID_TYPE_SLIME or
        grid.type == GRID_TYPE_APPRENTICE or
        grid.type == GRID_TYPE_MAP or
        grid.type == GRID_TYPE_BOSS_BOX or
        grid.type == GRID_TYPE_ACTIVITY_BOSS_BOX or
        grid.type == GRID_TYPE_SUMMON) and
        grid:isOpened() and grid.bonus and
        (not grid.monster or grid.monster:isDead()) then
        return true;
    end

    if grid.has_key and not grid.picked and (not grid.monster or grid.monster:isDead()) then
        return true;
    end

    return false;
end

-- 捡物品
function doPickup(grid)
    local pos = grid:getPos();

    -- 是否可拾取
    if canPickup(grid) then
        -- 物品
        if DungeonBossM.isBossLayer(DungeonM.getDungeonId(), DungeonM.currentLayer(), ME.user) then
            if DungeonM.isAllMonsterDead() then
                DungeonM.pickUp(pos);
                cclog("auto pick: " .. pos);
                return true;
            end
        else
            DungeonActionM.go("pick_item", pos);
            return true;
        end
    end

    -- 尝试捡钥匙
    if grid.has_key and not grid.picked and (not grid.monster or grid.monster:isDead()) then
        DungeonActionM.go("pick_item", pos);
        return true;
    end

    return false;
end

-- 进行一次捡取物品操作
function tryPickupOnce()
    for pos = 1, GRID_SIZE do
        local grid = DungeonM.getGridByPos(pos);
        if doPickup(grid) then
            return;
        end
    end
end

-- 间隔次数
local intervalCount =  2;

-- 次数
local currentCount = 0;

-- 拾取更新
function pickupUpdate()
    currentCount = currentCount + 1;
    if currentCount >= intervalCount then
        -- 不在地牢中
        if DungeonM.getState() ~= DUNGEON_GAMING then
            return;
        end

        tryPickupOnce();
        currentCount = 0;
    end
end


-- 开始自动拾取物品操作
function startAutoPickup(forceGrade)
    -- 如果已经在自动战斗中，不能再自动拾取
    if inFighting() then
        return;
    end

    -- 启动自定拾取定时器
    if not ScheduleM.isSchemeAlive("autoPickupSchedulerID") then
        ScheduleM.createScheme("autoPickupSchedulerID", pickupUpdate, 0.1, true);
    end

    -- 按压力度越大，频率越快
    if forceGrade >= FORCE_GRADE_4 then
        -- 震动下
        Vibrator.vibrate(1);
        intervalCount = 1;
    elseif forceGrade >= FORCE_GRADE_3 then
        intervalCount = 2;
    else
        intervalCount = 4;
    end
end

-- 停止自动拾取物品操作
function stopAutoPickup()
    ScheduleM.deleteScheme("autoPickupSchedulerID")
end


